MAKE_INTERP_SPLINE
Overview
The MAKE_INTERP_SPLINE function constructs an interpolating B-spline (basis spline) through a set of data points and evaluates it at new positions. B-splines are piecewise polynomial functions that provide smooth interpolation while maintaining local control—changes to data points only affect the curve in a limited region. This function is particularly useful for generating smooth curves through discrete measurements, resampling time series data, or creating differentiable approximations to tabular data.
This implementation uses SciPy’s make_interp_spline function from the scipy.interpolate module. The underlying algorithm constructs a BSpline object that passes exactly through each provided data point.
A B-spline of degree k is a piecewise polynomial function defined over a sequence of knots t_0, t_1, \ldots, t_m. The basis functions B_{i,k}(x) are computed using the Cox–de Boor recursion formula:
B_{i,0}(x) = \begin{cases} 1 & \text{if } t_i \leq x < t_{i+1} \\ 0 & \text{otherwise} \end{cases}
B_{i,k}(x) = \frac{x - t_i}{t_{i+k} - t_i} B_{i,k-1}(x) + \frac{t_{i+k+1} - x}{t_{i+k+1} - t_{i+1}} B_{i+1,k-1}(x)
The default degree is k = 3 (cubic spline), which provides C^2 continuity—the function and its first two derivatives are continuous at all knot locations. Higher degrees produce smoother curves but require more data points.
The bc_type parameter controls boundary conditions at the endpoints:
- not-a-knot (default): The first and second polynomial segments are the same polynomial, as are the last two. This is appropriate for general-purpose interpolation.
- clamped: First derivatives at both ends are zero, creating a curve that approaches endpoints horizontally.
- natural: Second derivatives at both ends are zero, minimizing overall curvature.
- periodic: The function and its first k-1 derivatives match at both endpoints, suitable for closed curves.
For more background on B-splines, see the Wikipedia article on B-splines and Carl de Boor’s foundational work A Practical Guide to Splines (Springer, 1978).
This example function is provided as-is without any representation of accuracy.
Excel Usage
=MAKE_INTERP_SPLINE(x, y, x_new, k, bc_type)
x(list[list], required): The x-coordinates of the data pointsy(list[list], required): The y-coordinates of the data pointsx_new(list[list], required): The x-coordinates at which to evaluate the splinek(int, optional, default: 3): B-spline degreebc_type(str, optional, default: “not-a-knot”): Boundary condition type
Returns (list[list]): A 2D list of interpolated values, or error message string.
Examples
Example 1: Demo case 1
Inputs:
| x | y | x_new | k |
|---|---|---|---|
| 0 | 0 | 0.5 | 1 |
| 1 | 1 | 1.5 | |
| 2 | 4 |
Excel formula:
=MAKE_INTERP_SPLINE({0;1;2}, {0;1;4}, {0.5;1.5}, 1)
Expected output:
| Result |
|---|
| 0.5 |
| 2.5 |
Example 2: Demo case 2
Inputs:
| x | y | x_new |
|---|---|---|
| 0 | 0 | 0.5 |
| 1 | 1 | 1.5 |
| 2 | 0 | 2.5 |
| 3 | 1 |
Excel formula:
=MAKE_INTERP_SPLINE({0;1;2;3}, {0;1;0;1}, {0.5;1.5;2.5})
Expected output:
| Result |
|---|
| 1 |
| 0.5 |
| 0 |
Example 3: Demo case 3
Inputs:
| x | y | x_new | k |
|---|---|---|---|
| 0 | 0 | 0.5 | 2 |
| 1 | 1 | 2.5 | |
| 2 | 4 | ||
| 3 | 9 |
Excel formula:
=MAKE_INTERP_SPLINE({0;1;2;3}, {0;1;4;9}, {0.5;2.5}, 2)
Expected output:
| Result |
|---|
| 0.25 |
| 6.25 |
Example 4: Demo case 4
Inputs:
| x | y | x_new | k | bc_type |
|---|---|---|---|---|
| 0 | 0 | 0.5 | 3 | clamped |
| 1 | 1 | 1.5 | ||
| 2 | 0 | 2.5 | ||
| 3 | -1 | 3.5 | ||
| 4 | 0 |
Excel formula:
=MAKE_INTERP_SPLINE({0;1;2;3;4}, {0;1;0;-1;0}, {0.5;1.5;2.5;3.5}, 3, "clamped")
Expected output:
| Result |
|---|
| 0.45 |
| 0.77 |
| -0.77 |
| -0.45 |
Python Code
import math
from scipy.interpolate import make_interp_spline as scipy_make_interp_spline
def make_interp_spline(x, y, x_new, k=3, bc_type='not-a-knot'):
"""
Compute interpolating B-spline and evaluate at new points.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.make_interp_spline.html
This example function is provided as-is without any representation of accuracy.
Args:
x (list[list]): The x-coordinates of the data points
y (list[list]): The y-coordinates of the data points
x_new (list[list]): The x-coordinates at which to evaluate the spline
k (int, optional): B-spline degree Default is 3.
bc_type (str, optional): Boundary condition type Valid options: not-a-knot, periodic, clamped, natural. Default is 'not-a-knot'.
Returns:
list[list]: A 2D list of interpolated values, or error message string.
"""
def to2d(val):
"""Convert scalar to 2D list if needed."""
return [[val]] if not isinstance(val, list) else val
def flatten(arr):
"""Flatten a 2D list to a 1D list."""
return [item for sublist in arr for item in sublist]
def validate_numeric_2d(arr, name):
"""Validate that arr is a 2D list of finite numbers."""
if not isinstance(arr, list):
return f"Invalid input: {name} must be a 2D list."
for i, row in enumerate(arr):
if not isinstance(row, list):
return f"Invalid input: {name} must be a 2D list (row {i} is not a list)."
for j, val in enumerate(row):
if not isinstance(val, (int, float)):
return f"Invalid input: {name}[{i}][{j}] must be a number."
if math.isnan(val) or math.isinf(val):
return f"Invalid input: {name}[{i}][{j}] must be finite."
return None
# Normalize inputs to 2D lists
x = to2d(x)
y = to2d(y)
x_new = to2d(x_new)
# Validate inputs
error = validate_numeric_2d(x, "x")
if error:
return error
error = validate_numeric_2d(y, "y")
if error:
return error
error = validate_numeric_2d(x_new, "x_new")
if error:
return error
# Validate k parameter
if not isinstance(k, (int, float)):
return "Invalid input: k must be an integer."
if k != int(k):
return "Invalid input: k must be an integer."
k = int(k)
if k < 0:
return "Invalid input: k must be non-negative."
# Validate bc_type parameter
valid_bc_types = ["not-a-knot", "periodic", "clamped", "natural"]
if bc_type not in valid_bc_types:
return f"Invalid input: bc_type must be one of {valid_bc_types}."
# Convert bc_type to None for scipy if it's 'not-a-knot' (scipy's default)
bc_type_scipy = None if bc_type == "not-a-knot" else bc_type
# Flatten 2D lists to 1D arrays
try:
x_flat = flatten(x)
y_flat = flatten(y)
x_new_flat = flatten(x_new)
except Exception as exc:
return f"Invalid input: unable to flatten arrays: {exc}"
# Check that x and y have the same length
if len(x_flat) != len(y_flat):
return f"Invalid input: x and y must have the same length (got {len(x_flat)} and {len(y_flat)})."
# Check minimum length requirement
if len(x_flat) < k + 1:
return f"Invalid input: need at least {k + 1} data points for degree {k} spline (got {len(x_flat)})."
# Check that x values are strictly increasing
for i in range(len(x_flat) - 1):
if x_flat[i] >= x_flat[i + 1]:
return "Invalid input: x values must be strictly increasing."
# Create and evaluate the spline
try:
spline = scipy_make_interp_spline(x_flat, y_flat, k=k, bc_type=bc_type_scipy)
result = spline(x_new_flat)
except Exception as exc:
return f"scipy.interpolate.make_interp_spline error: {exc}"
# Validate result
if not hasattr(result, "__iter__"):
result = [result]
# Convert result to 2D list (column vector)
try:
result_2d = [[float(val)] for val in result]
except Exception as exc:
return f"Error converting result to 2D list: {exc}"
# Check for non-finite values in result
for i, row in enumerate(result_2d):
for val in row:
if math.isnan(val) or math.isinf(val):
return f"scipy.interpolate.make_interp_spline error: result contains non-finite value at index {i}."
return result_2d